#ifndef CDRECONSTRUCTOR_H_
#define CDRECONSTRUCTOR_H_

#include <vector>
#include <fstream>
#include <iostream>

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>

#include <pcl/point_types.h>


class Facade;
class Window;
class FacadeReconstructor;
class OutlineReconstructor;
class Building;
class Cell;
class FacadeContainer;

class Parameters
{
public:
	enum FacadeR {CD};
	enum OutlineR {MANUAL,BOUNDING_BOX};

	FacadeR _facadeReconstructor;
	OutlineR _outlineReconstructor;
};

class CDParameters : public Parameters
{
public:
	float _image_resolution; //Binary-Bitmap Resolution in cm
	float _cornerDetectionRadius; //Corner-Point-Detection Radius
	float _clusteringVerMinPoints; //Min Points to define a cluster vertically
	float _clusteringVerDistance; //Distance for clustering vertically
	float _clusteringHorMinPoints; //Min Points to define a cluster Hor
	float _clusteringHorDistance; //Distance for clustering Hor
};

class Cell
{
public:
	Cell(int _h,int _v,double x,double y,double w, double h)
	{
		h_index=_h;
		v_index=_v;

		pos(0) = x; pos(1) = y;
		size(0) = w; size(1) = h;
		filled = 0;
	}
	~Cell(){}

	

	Eigen::Vector2f pos;
	Eigen::Vector2f size;
	int filled;
	int h_index;
	int v_index;

};

class BuildingReconstructor
{
public:
	BuildingReconstructor(std::vector<FacadeContainer*> fC) :
	  _facadeContainer(fC)
	{_outlineReconstructor=0;}

	bool reconstruct(Building *building);

	void setFacadeReconstructor(FacadeReconstructor* f)
	{ _facadeReconstructor=f; }

	void setOutlineReconstructor(OutlineReconstructor *o)
	{ _outlineReconstructor=o; }

	FacadeReconstructor* getFacadeReconstructor()
	{return _facadeReconstructor;}

	OutlineReconstructor* getOutlineReconstructor()
	{return _outlineReconstructor;}



private:
	FacadeReconstructor* _facadeReconstructor;
	OutlineReconstructor* _outlineReconstructor; 



	std::vector<FacadeContainer*> _facadeContainer;
};

class OutlineReconstructor
{
public:
	OutlineReconstructor(BuildingReconstructor* bR)
	{buildingReconstructor=bR;}

	virtual void reconstruct(Facade* f) = 0;

	void setInputCloud(pcl::PointCloud<pcl::PointXYZ>::Ptr pc)
	{
		_pointcloud=pc;
	}

	void setImageResolution(float res)
	{ _image_resolution = res; }

	float getImageResolution()
	{ return _image_resolution; }

protected:

	std::vector< Eigen::Vector2f* > _vertices;
	pcl::PointCloud<pcl::PointXYZ>::Ptr _pointcloud;

	float _image_resolution;

	BuildingReconstructor *buildingReconstructor;
};

class BBOutlineReconstructor : public OutlineReconstructor
{
public:
	BBOutlineReconstructor(BuildingReconstructor* bR) : OutlineReconstructor(bR)
	{
	
	}

	void reconstruct(Facade *f);

};

class ManualOutlineReconstructor : public OutlineReconstructor
{
public:
	ManualOutlineReconstructor(BuildingReconstructor* bR) : OutlineReconstructor(bR)
	{
	
	}

	void reconstruct(Facade *f);

	void setImageResolution(float res)
	{ _image_resolution = res; }

	float getImageResolution()
	{ return _image_resolution; }



};

class FacadeReconstructor
{

public:
	FacadeReconstructor(BuildingReconstructor* bR)
	{
		_buildingReconstructor = bR;
	}

	virtual bool reconstruct(Facade *facade) = 0;
	
	void setInputCloud(pcl::PointCloud<pcl::PointXYZ>::Ptr pc)
	{
		_pointcloud=pc;
	}

	pcl::PointCloud<pcl::PointXYZ>::Ptr getPointCloud()
	{
		return _pointcloud;
	}

	void setParameters(Parameters *p)
	{ _parameter=p; }

	Parameters* getParameters()
	{return _parameter;}

protected:
	BuildingReconstructor* _buildingReconstructor;

	Parameters* _parameter;

	pcl::PointCloud<pcl::PointXYZ>::Ptr _pointcloud;

	//TextureReconstructor* _textureReconstructor;

};



class CDReconstructor : public FacadeReconstructor
{
public:

	bool reconstruct(Facade *facade);

	CDReconstructor(BuildingReconstructor* bR) : FacadeReconstructor(bR),
		ver_indices ( new pcl::PointIndices ()),
		hor_indices ( new pcl::PointIndices ()),
		_pointcloud ( new pcl::PointCloud<pcl::PointXYZ> ),
		aligned(false)
	{

	}

	void reset();

	pcl::PointCloud<pcl::PointXYZ>::Ptr getCDPointCloud()
	{ return _pointcloud; }
	
	CDParameters* getParameters()
	{return _parameters;}
	
	void setParameters(CDParameters* parameters)
	{_parameters = parameters;}

	cv::Mat* detectCornerPoints(float radius,bool show);
	
	float getSamplingDistance();

	void drawSplits();

	cv::Mat* drawBitmapFromPointcloud(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud,float res);

	std::vector< std::vector<Cell*> > cells; //2d-cells

	Eigen::Vector4f min_p, max_p; //Minimum and Maximum Points for Bounding Box

	std::vector<Window*> createCCLWindows(Eigen::Vector4f min_p, Eigen::Vector4f max_p,Facade* f);

	cv::Mat* createBinaryBitmap(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud,int closing_size=1);
private:
	
	Eigen::Affine3f transformation; //Affine Transformation for local coordinate System

	CDParameters* _parameters; //Cell Decomposition Parameters

	pcl::PointCloud<pcl::PointXYZ>::Ptr _pointcloud;

	cv::Mat binary_image; //Binary Image

	

	pcl::PointIndices::Ptr ver_indices;
 	pcl::PointIndices::Ptr hor_indices;
	void align();
	
	void findSplitCluster(std::vector<double>& positions,std::vector<double>&splits,int min_points,float treshold);
	
	void createCells(std::vector<double> v_splits, std::vector<double> h_splits);
	void checkCells();
	Cell* getCell(int i, int j);
	bool isValid(int h,int v);
	



	

	double searchClosestSplit(double val, std::vector<double> splits);

	std::string createBumpmap(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud,float res, int dilateVal, std::string facadeID);

	//:D
	bool aligned;
	
};


#endif